/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Contributors:     Mathias Bogaert
//                   joelr@viair.com
package org.apache.log4j.helpers;

import org.apache.log4j.spi.LoggingEvent;

/**
 * <code>BoundedFIFO</code> serves as the bounded first-in-first-out buffer
 * heavily used by the {@link org.apache.log4j.AsyncAppender}.
 * 
 * @author Ceki G&uuml;lc&uuml;
 * @since version 0.9.1
 */
public class BoundedFIFO {

	LoggingEvent[] buf;
	int numElements = 0;
	int first = 0;
	int next = 0;
	int maxSize;

	/**
	 * Instantiate a new BoundedFIFO with a maximum size passed as argument.
	 */
	public BoundedFIFO(int maxSize) {
		if (maxSize < 1) {
			throw new IllegalArgumentException("The maxSize argument ("
					+ maxSize + ") is not a positive integer.");
		}
		this.maxSize = maxSize;
		buf = new LoggingEvent[maxSize];
	}

	/**
	 * Get the first element in the buffer. Returns <code>null</code> if there
	 * are no elements in the buffer.
	 */
	public LoggingEvent get() {
		if (numElements == 0)
			return null;

		LoggingEvent r = buf[first];
		buf[first] = null; // help garbage collection

		if (++first == maxSize) {
			first = 0;
		}
		numElements--;
		return r;
	}

	/**
	 * Place a {@link LoggingEvent} in the buffer. If the buffer is full then
	 * the event is <b>silently dropped</b>. It is the caller's responsability
	 * to make sure that the buffer has free space.
	 */
	public void put(LoggingEvent o) {
		if (numElements != maxSize) {
			buf[next] = o;
			if (++next == maxSize) {
				next = 0;
			}
			numElements++;
		}
	}

	/**
	 * Get the maximum size of the buffer.
	 */
	public int getMaxSize() {
		return maxSize;
	}

	/**
	 * Return <code>true</code> if the buffer is full, that is, whether the
	 * number of elements in the buffer equals the buffer size.
	 */
	public boolean isFull() {
		return numElements == maxSize;
	}

	/**
	 * Get the number of elements in the buffer. This number is guaranteed to be
	 * in the range 0 to <code>maxSize</code> (inclusive).
	 */
	public int length() {
		return numElements;
	}

	int min(int a, int b) {
		return a < b ? a : b;
	}

	/**
	 * Resize the buffer to a new size. If the new size is smaller than the old
	 * size events might be lost.
	 * 
	 * @since 1.1
	 */
	synchronized public void resize(int newSize) {
		if (newSize == maxSize)
			return;

		LoggingEvent[] tmp = new LoggingEvent[newSize];

		// we should not copy beyond the buf array
		int len1 = maxSize - first;

		// we should not copy beyond the tmp array
		len1 = min(len1, newSize);

		// er.. how much do we actually need to copy?
		// We should not copy more than the actual number of elements.
		len1 = min(len1, numElements);

		// Copy from buf starting a first, to tmp, starting at position 0, len1
		// elements.
		System.arraycopy(buf, first, tmp, 0, len1);

		// Are there any uncopied elements and is there still space in the new
		// array?
		int len2 = 0;
		if ((len1 < numElements) && (len1 < newSize)) {
			len2 = numElements - len1;
			len2 = min(len2, newSize - len1);
			System.arraycopy(buf, 0, tmp, len1, len2);
		}

		this.buf = tmp;
		this.maxSize = newSize;
		this.first = 0;
		this.numElements = len1 + len2;
		this.next = this.numElements;
		if (this.next == this.maxSize) // this should never happen, but again,
										// it just might.
			this.next = 0;
	}

	/**
	 * Returns <code>true</code> if there is just one element in the buffer. In
	 * other words, if there were no elements before the last {@link #put}
	 * operation completed.
	 */
	public boolean wasEmpty() {
		return numElements == 1;
	}

	/**
	 * Returns <code>true</code> if the number of elements in the buffer plus 1
	 * equals the maximum buffer size, returns <code>false</code> otherwise.
	 */
	public boolean wasFull() {
		return (numElements + 1 == maxSize);
	}

}
